package org.xq.softcup.mq.remoting.invoker.reference;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xq.softcup.mq.remoting.invoker.XQRpcInvokerFactory;
import org.xq.softcup.mq.remoting.invoker.call.CallType;
import org.xq.softcup.mq.remoting.invoker.call.XQRpcInvokeCallback;
import org.xq.softcup.mq.remoting.invoker.call.XQRpcInvokeFuture;
import org.xq.softcup.mq.remoting.invoker.generic.XQRpcGenericService;
import org.xq.softcup.mq.remoting.invoker.router.LoadBalance;
import org.xq.softcup.mq.remoting.net.Client;
import org.xq.softcup.mq.remoting.net.NetEnum;
import org.xq.softcup.mq.remoting.net.params.XQRpcFutureResponse;
import org.xq.softcup.mq.remoting.net.params.XQRpcRequest;
import org.xq.softcup.mq.remoting.net.params.XQRpcResponse;
import org.xq.softcup.mq.remoting.provider.XQRpcProviderFactory;
import org.xq.softcup.mq.serialize.Serializer;
import org.xq.softcup.mq.util.ClassUtil;
import org.xq.softcup.mq.util.XQRpcException;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Slf4j
public class XQRpcReferenceBean {
    private NetEnum netType;
    private Serializer serializer;
    private CallType callType;
    private LoadBalance loadBalance;
    private Class<?> iface;
    private String version;
    private long timeout = 1000L;
    private String address;
    private String accessToken;
    private XQRpcInvokeCallback invokeCallback;
    private XQRpcInvokerFactory invokerFactory;

    public XQRpcReferenceBean(NetEnum netType, Serializer serializer, CallType callType,
                              LoadBalance loadBalance, Class<?> iface, String version,
                              long timeout, String address, String accessToken,
                              XQRpcInvokeCallback invokeCallback, XQRpcInvokerFactory invokerFactory) {
        this.netType = netType;
        this.serializer = serializer;
        this.callType = callType;
        this.loadBalance = loadBalance;
        this.iface = iface;
        this.version = version;
        this.timeout = timeout;
        this.address = address;
        this.accessToken = accessToken;
        this.invokeCallback = invokeCallback;
        this.invokerFactory = invokerFactory;
        if (this.netType == null) {
            throw new XQRpcException("缺少netType引用.");
        }
        if (this.serializer == null) {
            throw new XQRpcException("缺少serializer引用.");
        }
        if (this.callType == null) {
            throw new XQRpcException("缺少callType引用.");
        }
        if (this.loadBalance == null) {
            throw new XQRpcException("缺少loadBalance引用.");
        }
        if (this.iface == null) {
            throw new XQRpcException("缺少iface引用.");
        }
        if (this.timeout < 0L) {
            this.timeout = 0L;
        }
        if (this.invokerFactory == null) {
            this.invokerFactory = XQRpcInvokerFactory.getInstance();
        }
        initClient();
    }

    public Serializer getSerializer() {
        return this.serializer;
    }

    public long getTimeout() {
        return this.timeout;
    }

    public XQRpcInvokerFactory getInvokerFactory() {
        return this.invokerFactory;
    }

    Client client = null;

    private void initClient() {
        try {
            this.client = ((Client) this.netType.clientClass.newInstance());
            this.client.init(this);
        } catch (InstantiationException | IllegalAccessException e) {
            throw new XQRpcException(e);
        }
    }

    /**
     * 动态代理获取引用对象
     * @return
     */
    public Object getObject() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{this.iface}, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String className = method.getDeclaringClass().getName();
                String verson1 = XQRpcReferenceBean.this.version;
                String methodName = method.getName();
                Class<?>[] parameterTypes = method.getParameterTypes();
                Object[] parameters = args;
                if ((className.equals(XQRpcGenericService.class.getName())) && (methodName.equals("invoke"))) {
                    Class<?>[] paramTypes = null;
                    if (args[3] != null) {
                        String[] paramTypes_str = (String[]) args[3];
                        if (paramTypes_str.length > 0) {
                            paramTypes = new Class[paramTypes_str.length];
                            for (int i = 0; i < paramTypes_str.length; i++) {
                                paramTypes[i] = ClassUtil.resolveClass(paramTypes_str[i]);
                            }
                        }
                    }
                    className = (String) args[0];
                    verson1 = (String) args[1];
                    methodName = (String) args[2];
                    parameterTypes = paramTypes;
                    parameters = (Object[]) args[4];
                }
                if (className.equals(Object.class.getName())) {
                    XQRpcReferenceBean.log.info("不支持该代理类方法[{}#{}]", className, methodName);
                    throw new XQRpcException("不支持该代理类方法！");
                }
                String finalAddress = XQRpcReferenceBean.this.address;
                if (((finalAddress == null) || (finalAddress.trim().length() == 0)) &&
                        (XQRpcReferenceBean.this.invokerFactory != null) && (XQRpcReferenceBean.this.invokerFactory.getServiceRegistry() != null)) {
                    String serviceKey = XQRpcProviderFactory.makeServiceKey(className, verson1);
                    TreeSet<String> addressSet = XQRpcReferenceBean.this.invokerFactory.getServiceRegistry().discovery(serviceKey);
                    if ((addressSet != null) && (addressSet.size() != 0)) {
                        if (addressSet.size() == 1) {
                            finalAddress = (String) addressSet.first();
                        } else {
                            finalAddress = XQRpcReferenceBean.this.loadBalance.xqRpcInvokerRouter.route(serviceKey, addressSet);
                        }
                    }
                }
                if ((finalAddress == null) || (finalAddress.trim().length() == 0)) {
                    throw new XQRpcException("引用类[" + className + "]地址为空！");
                }
                XQRpcRequest xqRpcRequest = new XQRpcRequest();
                xqRpcRequest.setRequestId(UUID.randomUUID().toString());
                xqRpcRequest.setCreateMillisTime(System.currentTimeMillis());
                xqRpcRequest.setAccessToken(XQRpcReferenceBean.this.accessToken);
                xqRpcRequest.setClassName(className);
                xqRpcRequest.setMethodName(methodName);
                xqRpcRequest.setParameterTypes(parameterTypes);
                xqRpcRequest.setParameters(parameters);
                if (CallType.SYNC == XQRpcReferenceBean.this.callType) { //同步方式
                    XQRpcFutureResponse futureResponse = new XQRpcFutureResponse(XQRpcReferenceBean.this.invokerFactory, xqRpcRequest, null);
                    try {
                        //发送网络请求
                        XQRpcReferenceBean.this.client.asyncSend(finalAddress, xqRpcRequest);

                        XQRpcResponse xqRpcResponse = futureResponse.get(XQRpcReferenceBean.this.timeout, TimeUnit.MILLISECONDS);
                        if (xqRpcResponse.getErrorMsg() != null) {
                            throw new XQRpcException(xqRpcResponse.getErrorMsg());
                        }
                        return xqRpcResponse.getResult();
                    } catch (Exception e) {
                        XQRpcReferenceBean.log.info("引用异常, 地址:{}, {}", finalAddress, xqRpcRequest);

                        throw ((e instanceof XQRpcException) ? e : new XQRpcException(e));
                    } finally {
                        futureResponse.removeInvokerFuture();
                    }
                }
                if (CallType.FUTURE == XQRpcReferenceBean.this.callType) { //异步方式
                    XQRpcFutureResponse futureResponse = new XQRpcFutureResponse(XQRpcReferenceBean.this.invokerFactory, xqRpcRequest, null);
                    try {
                        XQRpcInvokeFuture invokeFuture = new XQRpcInvokeFuture(futureResponse);
                        XQRpcInvokeFuture.setFuture(invokeFuture);


                        XQRpcReferenceBean.this.client.asyncSend(finalAddress, xqRpcRequest);

                        return null;
                    } catch (Exception e) {
                        XQRpcReferenceBean.log.info("引用异常, address:{}, {}", finalAddress, xqRpcRequest);
                        futureResponse.removeInvokerFuture();
                        throw ((e instanceof XQRpcException) ? e : new XQRpcException(e));
                    }
                }
                if (CallType.CALLBACK == XQRpcReferenceBean.this.callType) { //回调方式
                    XQRpcInvokeCallback finalInvokeCallback = XQRpcReferenceBean.this.invokeCallback;
                    XQRpcInvokeCallback threadInvokeCallback = XQRpcInvokeCallback.getCallback();
                    if (threadInvokeCallback != null) {
                        finalInvokeCallback = threadInvokeCallback;
                    }
                    if (finalInvokeCallback == null) {
                        throw new XQRpcException("XQRpcInvokeCallback（CallType=" + CallType.CALLBACK.name() + "）不能为空.");
                    }
                    XQRpcFutureResponse futureResponse = new XQRpcFutureResponse(XQRpcReferenceBean.this.invokerFactory, xqRpcRequest, finalInvokeCallback);
                    try {
                        XQRpcReferenceBean.this.client.asyncSend(finalAddress, xqRpcRequest);
                    } catch (Exception e) {
                        XQRpcReferenceBean.log.info("引用异常, address:{}, {}", finalAddress, xqRpcRequest);
                        futureResponse.removeInvokerFuture();
                        throw ((e instanceof XQRpcException) ? e : new XQRpcException(e));
                    }
                    return null;
                }
                if (CallType.ONEWAY == XQRpcReferenceBean.this.callType) {
                    XQRpcReferenceBean.this.client.asyncSend(finalAddress, xqRpcRequest);
                    return null;
                }
                throw new XQRpcException("不支持类型callType[" + XQRpcReferenceBean.this.callType + "]");
            }
        });
    }

    public Class<?> getObjectType() {
        return this.iface;
    }
}
